package cn.anecansaitin.firecrafting.common.world;

import cn.anecansaitin.firecrafting.api.common.crafting.IFireRecipe;
import cn.anecansaitin.firecrafting.api.common.state.enums.RecipeActionResult;
import cn.anecansaitin.firecrafting.common.Config;
import com.google.common.collect.Maps;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Registry;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.Tag;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Recipe;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.saveddata.SavedData;
import net.minecraftforge.registries.ForgeRegistries;
import org.apache.logging.log4j.util.BiConsumer;
import org.jetbrains.annotations.NotNull;

import java.util.*;
import java.util.function.Consumer;

public class FireCraftingSavedData extends SavedData {
    private Map<String, TimedItems> stacksInPos;
    private List<TimedTask> timedTasks;
    private static final String NAME = "FireCraft";
    private static final String KEY_COUNT = "count";
    private static final String KEY_ITEMS = "items";
    private static final String KEY_TIME = "time";
    private static final String KEY_ROOT = "fc";

    //构造函数
    public FireCraftingSavedData(Map<String, TimedItems> stacksInPos) {
        this.stacksInPos = stacksInPos;
    }

    //获取data
    public static FireCraftingSavedData getInstance(ServerLevel serverWorld) {
        return serverWorld.getDataStorage().computeIfAbsent(FireCraftingSavedData::read, () -> new FireCraftingSavedData(Maps.newHashMap()), NAME);
    }

    //添加物品
    public boolean addItem(BlockPos pos, ItemStack stack) {
        //按照坐标获取物品列表
        TimedItems timedItems = stacksInPos.getOrDefault(pos.toShortString(), new TimedItems());
        //如果是新创建的TimedItems则放入map中
        if (timedItems.isEmpty()) {
            stacksInPos.put(pos.toShortString(), timedItems);
        }
        //记录保存
        setDirty();
        //放入物品
        int count = stack.getCount();
        //循环逐个放入
        for (int i = 0; i < count; i++) {
            timedItems.addItem(stack.split(1));
        }

        return stack.isEmpty() || stack.getCount() < count;
    }

    //获取指定坐标物品数组
    public List<ItemStack> getStacks(BlockPos pos) {
        if (stacksInPos.containsKey(pos.toShortString())) {
            return stacksInPos.get(pos.toShortString()).getItems();
        }
        return new ArrayList<>();
    }

    //获取坐标物品集合
    public Map<String, TimedItems> getStacksInPos() {
        return stacksInPos;
    }

    //设置坐标物品集合
    public void setStacksInPos(Map<String, TimedItems> stacksInPos) {
        this.stacksInPos = stacksInPos;
        //记录保存
        setDirty();
    }

    //从map移除键值对
    public void removeByPos(BlockPos pos) {
        stacksInPos.remove(pos.toShortString());
    }

    //每tick触发进行的行为
    public void tick() {
        //无任何记录，不执行
        if (stacksInPos.isEmpty()) {
            return;
        }
        //存储需删除的坐标
        ArrayList<String> poss = new ArrayList<>();
        //触发计时器
        stacksInPos.forEach((key, value) -> {
            boolean remove = value.tick();
            if (remove) {
                //存储坐标
                poss.add(key);
            }
        });
        //删除坐标
        poss.forEach((p) -> stacksInPos.remove(p));
        //记录保存
        setDirty();
    }

    //保存
    @Override
    public @NotNull CompoundTag save(@NotNull CompoundTag tag) {
        Set<Map.Entry<String, TimedItems>> set = stacksInPos.entrySet();
        Iterator<Map.Entry<String, TimedItems>> iterator = set.iterator();
        int size = set.size();
        //创建一个tag当作数据结构的根
        CompoundTag root = new CompoundTag();
        //存入map中元素数量
        root.putInt(KEY_COUNT, size);
        //创建一个tag用于存放物品列表
        CompoundTag itemsTag = new CompoundTag();
        //创建数列存储所有计时器时间
        int[] times = new int[size];
        //将坐标与物品存入tag
        for (int i = 0; i < size; i++) {
            Map.Entry<String, TimedItems> entry = iterator.next();
            //存入坐标值
            itemsTag.putString(String.valueOf(i), entry.getKey());
            //存入计时器时间
            times[i] = entry.getValue().getTime();
            //创建列表tag存放物品
            ListTag items = new ListTag();
            //读取物品列表
            List<ItemStack> value = entry.getValue().getItems();
            for (int j = 0; j < value.size(); j++) {
                ItemStack stack = value.get(j);
                items.add(j, stack.serializeNBT());
            }
            //将items放入itemsTag
            itemsTag.put(String.valueOf(-i), items);
        }
        //时间数列存入itemsTag
        itemsTag.putIntArray(KEY_TIME, times);
        //物品tag存入root
        root.put(KEY_ITEMS, itemsTag);
        //root存入tag
        tag.put(KEY_ROOT, root);
        return tag;
    }

    //读取，与保存操作完全相反
    public static FireCraftingSavedData read(CompoundTag tag) {
        //获取root
        CompoundTag root = tag.getCompound(KEY_ROOT);
        //获取数量
        int count = root.getInt(KEY_COUNT);
        //获取itemsTag
        CompoundTag itemsTag = root.getCompound(KEY_ITEMS);
        //获取计时器时间数列
        int[] times = itemsTag.getIntArray(KEY_TIME);
        //新建map存储结果
        HashMap<String, TimedItems> stacksInPos = new HashMap<>();
        //读取itemsTag
        for (int i = 0; i < count; i++) {
            //读取坐标
            String pos = itemsTag.getString(String.valueOf(i));
            //读取物品
            //新建List存储物品
            ArrayList<ItemStack> stacks = new ArrayList<>();
            //循环读取
            for (Tag item : itemsTag.getList(String.valueOf(-i), Tag.TAG_COMPOUND)) {
                stacks.add(ItemStack.of((CompoundTag) item));
            }
            //创建TimedItems用以保存
            TimedItems timedItems = new TimedItems();
            //为TimedItems设置值
            timedItems.setItems(stacks);
            timedItems.setTime(times[i]);
            //读取到的坐标与对应物品列表存入map中
            stacksInPos.put(pos, timedItems);
        }
        //返回包含数据的data
        return new FireCraftingSavedData(stacksInPos);
    }

    //包装的物品List，包含了物品列表与一个计时器
    private static class TimedItems {
        private int time = 0;
        private ArrayList<ItemStack> items = new ArrayList<>();

        //添加物品到List，添加成功返回true
        public void addItem(ItemStack stack) {
            //只要交互了就让时间刷新，防止当玩家把火焰或岩浆当垃圾桶时重复刷新列表，进而重复触发配方检索
            time = Config.WORLD_MAX_TIME.get();
            //未达到上限进行添加
            if (items.size() < Config.WORLD_MAX_COUNT.get()) {
                items.add(stack);
            }
        }

        //判断物品列表是否为空
        public boolean isEmpty() {
            return items.isEmpty();
        }

        //获取物品列表
        public List<ItemStack> getItems() {
            return items;
        }

        //设置物品列表
        public void setItems(ArrayList<ItemStack> items) {
            this.items = items;
        }

        //每次调用让计时器减1，当减少为-1时返回true
        public boolean tick() {
            return --time == -1;
        }

        //获取计时器时间
        public int getTime() {
            return time;
        }

        //设置计时器时间
        public void setTime(int time) {
            this.time = time;
        }
    }

    private static class TimedTask {
        private int time;
        private final int interval;
        private final BlockPos pos;
        private final Level world;
        private final IFireRecipe task;

        public TimedTask(int time, BlockPos pos, int interval, Level world, IFireRecipe task) {
            this.time = time;
            this.interval = interval;
            this.pos = pos;
            this.world = world;
            this.task = task;
        }

        public TimedTask(int interval, int times, BlockPos pos, Level world, IFireRecipe task) {
            time = interval * times;
            this.interval = interval;
            this.pos = pos;
            this.world = world;
            this.task = task;
        }

        public RecipeActionResult action() {
            if (time % interval == 0) {
                if (task.matchFire(pos.below(), world)) {
                    time--;
                    return task.doAction(pos, world);
                } else {
                    return RecipeActionResult.FIRE_MISMATCH;
                }
            } else {
                time--;
                return RecipeActionResult.TIME_MISMATCH;
            }
        }

        public CompoundTag save() {
            CompoundTag tag = new CompoundTag();
            tag.putInt("time", time);
            tag.putInt("interval", interval);
            tag.putIntArray("pos", new int[]{pos.getX(), pos.getY(), pos.getZ()});
            tag.putString("task", task.getId().toString());
            return tag;
        }

        public static TimedTask read(CompoundTag tag, ServerLevel world) {
            int time = tag.getInt("time");
            int interval = tag.getInt("interval");
            int[] pos = tag.getIntArray("pos");
            String task = tag.getString("task");
            Optional<? extends Recipe<?>> recipe = world.getRecipeManager().byKey(new ResourceLocation(tag.getString(task)));
            return new TimedTask(time, new BlockPos(pos[0], pos[1], pos[2]), interval, world, (IFireRecipe) recipe.orElseThrow(() -> new RuntimeException("世界信息读取出现了问题，配方id发生了变化    Level Data error, recipe id  has changed")));
        }
    }
}
